import Config from "../config/Config"
import Base from "../base/Base"
import SnakeNode from "../snake/SnakeNode"
import Food, { FoodType } from "./Food"
import Snake from "../snake/Snake"
import PlayerSnake from "../snake/PlayerSnake"
import Rect from "../base/Rect"
import Chunk from "./Chunk"
import Circle from "../base/Circle"
import AISnake from "../snake/AISnake"
import { RandUtil } from "../util/RandUtil"
import { ISize } from "../util/V2Util"
import Timer from "../../core/Timer"
import Game from "../Game"
import SnakeHead from "../snake/SnakeHead"
import { Util } from "../util/Util"
import IMsg, { IMsgPlayer, MsgType } from "./Msg"
import { GameEvent } from "../event/GameEvent"

const ru = RandUtil
const util = Util
const cfg = Config.world
const chunkCfg = Config.chunk
const et = GameEvent.Type

/** 生成食物计时 */
const t_spawnFoods = "world_spawnFoods"
/** 清理战利品计时 */
const t_clearFoods = "world_clearFoods"

/** 游戏世界类 */
export default class World {

    /** 世界大小 */
    private _size: ISize = cfg.size
    /** 长边 */
    private _longSide: number = Math.max(this._size.w, this._size.h)
    /** 区块切割网格边数量 */
    private _split: number = Math.floor(this._longSide / chunkCfg.size)
    /** 世界形状 */
    private _base: Base = new Rect(this._size)
    /** 半径 */
    private _radius: ISize = { w: this._size.w / 2, h: this._size.h / 2 }
    /** 区块大小 */
    private _chunkSize: ISize = { w: this._size.w / this._split, h: this._size.h / this._split }
    /** 区块半径 */
    private _chunkRadius: ISize = { w: this._chunkSize.w / 2, h: this._chunkSize.h / 2 }
    /** 区块集合 */
    private _chunks: Chunk[][] = []
    /** 可生成食物的区块集合 */
    private _spawnFoodChunks: Chunk[] = []
    /** 蛇映射 <蛇id,蛇> */
    private _snakeMap = new Map<number, Snake>()
    /** 蛇映射 <蛇名,蛇>*/
    private _snakeNameMap = new Map<string, number>()
    /** 玩家映射 <玩家id,玩家> */
    private _playerMap = new Map<number, PlayerSnake>()
    /** 食物集合 <食物类型,食物集合> */
    private _foodMap = new Map<FoodType, Set<Food>>()
    /** 当前唯一实体id */
    private _currSnakeNodeId: number = 1
    /** 当前唯一食物id */
    private _currFoodId: number = 1
    /** 当前唯一蛇id */
    private _currSnakeId: number = 1
    /** 被吃的食物映射 <食物id,吃食物的蛇节点id> */
    private _eatenFoodMap = new Map<number, number>()
    /** 被吃食物被提取标识 */
    private _isExtractEatenFoodMap = false
    /** 排行榜 */
    private _rankArr: Snake[] = []
    /** 消息集合 */
    private _msgs: IMsg<any>[] = []
    /** 计时器 */
    private _timer: Timer = new Timer()

    /** 实例化世界 */
    constructor(
        /** 游戏实例 */
        readonly game: Game) {

        //创建区块
        for (let i = 0; i < this._split; i++) {
            this._chunks[i] = []
            for (let j = 0; j < this._split; j++) {
                let x = (i * this._chunkSize.w + this._chunkRadius.w) - this._radius.w
                let y = (j * this._chunkSize.h + this._chunkRadius.h) - this._radius.h
                this._chunks[i][j] = new Chunk({ world: this, x, y, w: this._chunkSize.w, h: this._chunkSize.h })
                this._spawnFoodChunks.push(this._chunks[i][j])
            }
        }

        //创建食物集合
        this._foodMap.set(FoodType.normal, new Set())
        this._foodMap.set(FoodType.snake, new Set())
    }

    /** 初始化 */
    start() {
        this._timer.set(t_spawnFoods, cfg.spawnFoodsTime, cfg.spawnFoodsTime)
        this._timer.set(t_clearFoods, cfg.clearFoodsTime, cfg.clearFoodsTime)
        //生成食物
        this.spawnFood(cfg.foodMax)
        //生成电脑
        for (let i = 0; i < cfg.computerCount; i++) {
            let ai = new AISnake({ world: this })
            this.addSnake(ai);
            ai.spawn()
        }
        //发送事件
        this.ed.send(et.WORLD_START, { world: this })
    }

    /** 每帧更新 */
    update() {
        if (this._isExtractEatenFoodMap) {
            this._isExtractEatenFoodMap = false
            this._eatenFoodMap.clear()
        }

        //清理战利品食物
        this.clearSnakeFoods()
        //生成食物
        this.spawnFood();

        //蛇逻辑
        this._snakeMap.forEach(s => s.update())
        this._snakeMap.forEach(s => s.updateAfterMove())
        this._snakeMap.forEach(s => s.updateAfterCheck())
    }

    /** 判断物体是否撞墙 */
    collideWall(obj: Base) {
        return !obj.in(this._base)
    }

    /** 生成食物 */
    spawnFood(count?: number) {
        this._timer.check(t_spawnFoods, () => {
            //计算生成数量
            count = count || cfg.spawnFoodCount + this._snakeMap.size * cfg.spawnFoodCount2
            //随机生成食物
            while (this._spawnFoodChunks.length > 0 &&
                this._foodMap.get(FoodType.normal).size < cfg.foodMax && count-- > 0) {
                let chunk = ru.choice(this._spawnFoodChunks)
                let rangex = [chunk.x - this._chunkRadius.w, chunk.x + this._chunkRadius.w]
                let rangey = [chunk.y - this._chunkRadius.h, chunk.y + this._chunkRadius.h]
                this.addFood(Food.create({ world: this, x: ru.rand2(rangex), y: ru.rand2(rangey) }))
            }
        }, (time, extra) => Date.now() + time - extra)
    }

    /** 定期清除战利品食物 */
    private clearSnakeFoods() {
        this._timer.check(t_clearFoods, () => {
            this._foodMap.get(FoodType.snake).forEach(f => {
                f.timerDestory()
            })
        })
    }

    /** 添加食物 true:添加成功 false:撞墙销毁*/
    addFood(food: Food) {
        //撞墙不添加
        if (this.collideWall(food)) {
            food.recover()
            return false
        }
        //加入到食物集合
        this._foodMap.get(food.type).add(food)
        food.updateChunk()
        return true
    }

    /** 移除食物 */
    removeFood(food: Food, eater?: SnakeHead) {
        //从区块中移除
        food.chunks.forEach(chunk => chunk.removeFood(food))
        //从食物集合中移除
        this._foodMap.get(food.type).delete(food)
        if (eater) {
            this._eatenFoodMap.set(food.id, eater.id)
        }
        food.recover()
    }

    /** 提取被吃的食物映射 <食物id,吃食物的蛇节点id> */
    extractEatenFoodMap() {
        this._isExtractEatenFoodMap = true
        return this._eatenFoodMap
    }

    /** 添加蛇 */
    addSnake(snake: Snake) {
        if (snake instanceof PlayerSnake) {
            this._playerMap.set(snake.id, snake)
            this.addMsg<IMsgPlayer>({
                type: MsgType.player,
                data: {
                    snakeId: snake.id,
                    flag: 1
                }
            })
        }
        let name = snake.name
        if (this._snakeNameMap.has(name)) {
            name += "_" + snake.id
        }
        this._snakeMap.set(snake.id, snake)
        this._snakeNameMap.set(snake.name, snake.id)
        this.ed.send(et.SNAKE_ADD, { snake })
    }

    /** 移除蛇 */
    removeSnake(id: number) {
        let snake = this._snakeMap.get(id)
        if (snake) {
            snake.die(this)
            this._snakeMap.delete(id)
            this._snakeNameMap.delete(snake.name)
            if (snake instanceof PlayerSnake) {
                this._playerMap.delete(id)
                this.addMsg<IMsgPlayer>({
                    type: MsgType.player,
                    data: {
                        snakeId: snake.id,
                        flag: -1
                    }
                })
            }
            this.ed.send(et.SNAKE_REMOVE, { snake })
        }
    }

    /** 添加可生成食物区块 */
    addSpawnFoodChunk(chunk: Chunk) {
        this._spawnFoodChunks.push(chunk)
    }

    /** 移除可生成食物区块 */
    removeSpawnFoodChunk(chunk: Chunk) {
        util.arrRemoveItem(this._spawnFoodChunks, chunk)
    }

    /** 更新排行榜 */
    updateRank() {
        this._rankArr = this.snakes.sort((a, b) => b.score - a.score)
        this._rankArr.forEach((snake, idx) => {
            snake.rank = idx + 1
        })
        this.ed.send(et.RANK_UPDATE, { rankArr: this._rankArr })
    }

    /** 添加消息 */
    addMsg<T>(msg: IMsg<T>) {
        this._msgs.push(msg)
    }

    /** 提取消息 */
    extractMsgs() {
        let extract = this._msgs
        this._msgs = []
        return extract
    }

    // ================================================================

    /** 帧间隔时间 单位s */
    get dt() { return this.game.dt }

    /** 事件派发器 */
    get ed() { return this.game.ed }

    /** 世界形状 */
    get base() { return this._base }

    /** 世界大小 */
    get size() { return this._size }

    /** 玩家id映射 */
    get playerMap() { return this._playerMap }

    /** 蛇id映射 */
    get snakeMap() { return this._snakeMap }

    /** 蛇集合 */
    get snakes() { return Array.from(this._snakeMap.values()) }

    /** 排行榜 */
    get rankArr() { return this._rankArr }

    /** 获取区块内的蛇 */
    getSnakes(chunks: Chunk[]): Snake[] {
        let snakes: Snake[] = []
        chunks.forEach(chunk => snakes = snakes.concat(chunk.snakes))
        return util.unique(snakes);
    }

    /** 获取区块内的蛇头部节点 */
    getSnakeHeads(chunks: Chunk[]) {
        let heads: SnakeHead[] = []
        chunks.forEach(chunk => heads = heads.concat(chunk.snakeHeads))
        return util.unique(heads)
    }

    /** 获取区块内的蛇节点 */
    getSnakeNodes(chunks: Chunk[]) {
        let nodes: SnakeNode[] = []
        chunks.forEach(chunk => nodes = nodes.concat(chunk.snakeNodes))
        return util.unique(nodes)
    }

    /** 获取区块内包含指定蛇集合的蛇节点 */
    getSnakeNodesInclude(chunks: Chunk[], include: Snake[]) {
        let nodes: SnakeNode[] = []
        chunks.forEach(chunk => nodes = nodes.concat(chunk.getSnakeNodesInclude(include)))
        return util.unique(nodes)
    }

    /** 获取区块内不包含指定蛇集合的蛇节点 */
    getSnakeNodesExclude(chunks: Chunk[], exclude: Snake[]) {
        let nodes: SnakeNode[] = []
        chunks.forEach(chunk => nodes = nodes.concat(chunk.getSnakeNodesExclude(exclude)))
        return util.unique(nodes)
    }

    /** 获取区块内的蛇节点映射 */
    getSnakeNodeMap(chunks: Chunk[]) {
        let snakeNodes = new Map<Snake, SnakeNode[]>();
        chunks.forEach(chunk =>
            chunk.snakeNodeMap.forEach((nodes, snake) => {
                let _nodes = snakeNodes.get(snake) || []
                snakeNodes.set(snake, util.arrConcatSet(_nodes, nodes))
            })
        )
        //去重
        snakeNodes.forEach((ns, s) => snakeNodes.set(s, util.unique(ns)))
        return snakeNodes;
    }

    /** 获取区块内包含指定蛇集合的蛇节点映射 */
    getSnakeNodeMapInclude(chunks: Chunk[], include: Snake[]) {
        let snakeNodes = new Map<Snake, SnakeNode[]>();
        chunks.forEach(chunk =>
            chunk.getSnakeNodeMapInclude(include).forEach((nodes, snake) => {
                let _nodes = snakeNodes.get(snake) || []
                snakeNodes.set(snake, util.arrConcatSet(_nodes, nodes))
            })
        )
        //去重
        snakeNodes.forEach((ns, s) => snakeNodes.set(s, util.unique(ns)))
        return snakeNodes;
    }

    /** 获取区块内不包含指定蛇集合的蛇节点映射 */
    getSnakeNodeMapExclude(chunks: Chunk[], exclude: Snake[]) {
        let snakeNodes = new Map<Snake, SnakeNode[]>();
        chunks.forEach(chunk =>
            chunk.getSnakeNodeMapExclude(exclude).forEach((nodes, snake) => {
                let _nodes = snakeNodes.get(snake) || []
                snakeNodes.set(snake, util.arrConcatSet(_nodes, nodes))
            })
        )
        //去重
        snakeNodes.forEach((ns, s) => snakeNodes.set(s, util.unique(ns)))
        return snakeNodes;
    }

    /** 根据名字获取蛇 */
    getSnake(name: string) {
        return this._snakeMap.get(this._snakeNameMap.get(name))
    }

    /** 获取区块内的食物 */
    getFoods(chunks: Chunk[]) {
        let foods: Food[] = []
        chunks.forEach(c => foods = foods.concat(c.foods))
        return util.unique(foods)
    }

    /** 获取出生点 */
    getSpawnPos() {
        let scfg = Config.snake
        let width = scfg.initLen * scfg.initWidth * scfg.initSpacing
        let nodeAway = 2.33 * width
        let headAway = 2.88 * width
        let wallAwayX = 2.66 * width
        let wallAwayY = wallAwayX
        let { w, h } = this._size
        let { w: rw, h: rh } = this._radius
        if (w < wallAwayX) {
            wallAwayX = ru.rand(w * 0.33, w * 0.5)
        }
        if (h < wallAwayY) {
            wallAwayY = ru.rand(h * 0.33, h * 0.5)
        }
        let randx = [-rw + wallAwayX, rw - wallAwayX]
        let randy = [-rh + wallAwayY, rh - wallAwayY]
        let pos = new Circle({ w: nodeAway })
        let count = 88
        while (count-- > 0) {
            pos.set({
                x: ru.rand2(randx),
                y: ru.rand2(randy),
            })
            let hpos = pos.cp().setW(headAway)
            //在蛇头附近重新随机
            if (this.getSnakes(this.getChunks(hpos)).some(s => hpos.collide(s.head)))
                continue
            //撞蛇重新随机
            else {
                let flag = false
                this.getSnakeNodeMap(this.getChunks(pos)).forEach(ns => {
                    if (!flag && ns.some(n => pos.collide(n))) {
                        flag = true
                    }
                })
                if (flag) continue
                else return pos
            }
        }
        //找不到出生点则找到最少节点的区块
        let chunk = this.getChunks(this._base).sort((a, b) => a.snakeNodeCount - b.snakeNodeCount)[0]
        let csize = Config.chunk.size
        let hs = csize / 2
        pos.set({
            x: ru.rand(chunk.x - hs, chunk.x + hs),
            y: ru.rand(chunk.y - hs, chunk.y + hs)
        })
        return pos
    }

    /** 获取坐标点所在的区块 */
    getChunk(pos: Base) {
        let x = util.range(Math.floor((pos.x + this._radius.w) / this._chunkSize.w),
            0, this._split - 1)
        let y = util.range(Math.floor((pos.y + this._radius.h) / this._chunkSize.h),
            0, this._split - 1)
        return this._chunks[x][y];
    }

    /** 获取物体所覆盖的区块 */
    getChunks(obj: Base) {
        //获取对应区块
        let chunk = this.getChunk(obj);
        let chunks = [chunk];
        //物体不跨区块直接返回该区块数组
        if (obj.in(chunk)) return chunks;
        //否则计算所覆盖的区块范围
        let { w, h } = this._chunkSize
        let { w: rw, h: rh } = this._radius
        let vs = new Rect(obj).offsetPos({ x: rw, y: rh }).vertexs
        let lx = Math.floor(vs[2].x / w)
        let uy = Math.floor(vs[2].y / h)
        let rx = Math.floor(vs[0].x / w)
        let by = Math.floor(vs[0].y / h)

        //遍历区块范围
        for (let x = lx; x <= rx; x++) {
            //跳过超出区块横向的范围
            if (x < 0 || x >= this._split) continue
            for (let y = uy; y <= by; y++) {
                //跳过超出区块纵向的范围
                if (y < 0 || y >= this._split) continue;
                let ck = this._chunks[x][y];
                if (ck == chunk) continue;
                //如果接触到区块则加进区块数组
                if (obj.collide(ck)) {
                    chunks.push(ck);
                }
            }
        }

        return chunks;
    }

    /** 获取一个蛇节点唯一id */
    getNewSnakeNodeId(): number {
        return this._currSnakeNodeId++
    }

    /** 获取一个蛇唯一id */
    getNewSnakeId(): number {
        return this._currSnakeId++
    }

    /** 获取一个食物唯一id */
    getNewFoodId(): number {
        return this._currFoodId++
    }


}